from fastapi import APIRouter, HTTPException, status, Query
from schemas.user_schemas import (
    UserLoginRequest,
    UserRegisterRequest,
    LoginResponse,
    RegisterResponse,
    UserResponse,
    RefreshTokenRequest,
    RefreshTokenResponse
)
from models.m import User
from utils.auth import (
    create_access_token,
    create_refresh_token,
    verify_token
)
from utils.user_factory import LocalUserFactory, OAuthUserFactory

from urllib.parse import quote
import requests

users_router = APIRouter(prefix="/users", tags=["用户认证"])


@users_router.post(
    "/register",
    response_model=RegisterResponse,
    status_code=status.HTTP_201_CREATED,
    summary="用户注册",
    description="使用用户名和密码注册新账号"
)
async def register(user_data: UserRegisterRequest):
    """
    用户注册接口

    - **username**: 用户名（3-64个字符，只能包含字母、数字、下划线）
    - **password**: 密码（至少6个字符）
    """
    # 使用抽象工厂模式创建本地用户
    try:
        # 实例化具体工厂
        factory = LocalUserFactory()
        user = await factory.create_user(
            username=user_data.username,
            password=user_data.password
        )
    except ValueError as e:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=str(e)
        )

    return RegisterResponse(
        message="注册成功",
        user=UserResponse.model_validate(user)
    )


@users_router.post(
    "/login",
    response_model=LoginResponse,
    summary="用户登录",
    description="使用用户名和密码登录，返回 JWT Token"
)
async def login(login_data: UserLoginRequest):
    """
    用户登录接口

    - **username**: 用户名
    - **password**: 密码

    **返回**:
    - access_token: JWT Token（用于后续 API 认证）
    - token_type: Token 类型（bearer）
    - user: 用户基本信息
    """
    # 查找用户
    user = await User.get_or_none(username=login_data.username)

    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="用户名不存在",
            headers={"WWW-Authenticate": "Bearer"},
        )

    # 验证密码（明文比对）
    if login_data.password != user.password:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="密码错误",
            headers={"WWW-Authenticate": "Bearer"},
        )

    # 生成 JWT Token 和 Refresh Token
    token_data = {"sub": str(user.id), "username": user.username}
    access_token = create_access_token(data=token_data)
    refresh_token = create_refresh_token(data=token_data)

    return LoginResponse(
        token=access_token,
        refresh_token=refresh_token,
        user=UserResponse.model_validate(user)
    )


@users_router.post(
    "/refresh",
    response_model=RefreshTokenResponse,
    summary="刷新 Token",
    description="使用 Refresh Token 获取新的 Access Token 和 Refresh Token"
)
async def refresh_token(refresh_data: RefreshTokenRequest):
    """
    刷新 Token 接口

    - **refresh_token**: Refresh Token

    **返回**:
    - token: 新的 Access Token
    - refresh_token: 新的 Refresh Token
    """
    # 验证 Refresh Token
    payload = verify_token(refresh_data.refresh_token, token_type="refresh")
    if not payload:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Refresh Token 无效或已过期",
            headers={"WWW-Authenticate": "Bearer"},
        )

    # 获取用户信息
    user_id = payload.get("sub")
    username = payload.get("username")
    
    if not user_id or not username:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Refresh Token 数据无效",
            headers={"WWW-Authenticate": "Bearer"},
        )

    # 生成新的 Token
    token_data = {"sub": user_id, "username": username}
    new_access_token = create_access_token(data=token_data)
    new_refresh_token = create_refresh_token(data=token_data)

    return RefreshTokenResponse(
        token=new_access_token,
        refresh_token=new_refresh_token
    )


# ==================== 钉钉第三方登录 ====================

# 钉钉配置
DINGTALK_CLIENT_ID = "ding93vlzrkppjsfu1v8"
DINGTALK_CLIENT_SECRET = "pkiHjIC1nGBtOkxshVhmPI9z2phlyLg-hgreySL07Q7zlulVYfW32stdXOg-_8bn"
DINGTALK_REDIRECT_URI = "http://localhost:5173/dingtalk-callback"


@users_router.get(
    "/dingtalklogin",
    summary="获取钉钉登录授权URL",
    description="返回钉钉OAuth2授权登录URL，前端跳转到此URL进行授权"
)
async def dingtalk_login():
    """
    钉钉第三方登录 - 获取授权URL
    
    **返回**:
    - url: 钉钉授权登录URL，前端需跳转到此URL
    """
    params = [
        f"redirect_uri={quote(DINGTALK_REDIRECT_URI)}",
        "response_type=code",
        f"client_id={DINGTALK_CLIENT_ID}",
        "scope=openid",
        "prompt=consent"
    ]
    url = "https://login.dingtalk.com/oauth2/auth?" + ("&".join(params))
    return {"url": url}


@users_router.get(
    "/dingtalkcallback",
    response_model=LoginResponse,
    summary="钉钉登录回调",
    description="钉钉OAuth2授权回调接口，处理授权码并完成登录"
)
async def dingtalk_callback(code: str = Query(..., description="钉钉授权码")):
    """
    钉钉第三方登录 - 回调接口
    
    - **code**: 钉钉授权码（由钉钉回调自动携带）
    
    **返回**:
    - token: Access Token
    - refresh_token: Refresh Token
    - user: 用户信息
    """
    try:
        # 1. 根据 authCode 获取用户 accessToken
        token_data = {
            "clientId": DINGTALK_CLIENT_ID,
            "clientSecret": DINGTALK_CLIENT_SECRET,
            "code": code,
            "grantType": "authorization_code"
        }
        token_resp = requests.post(
            'https://api.dingtalk.com/v1.0/oauth2/userAccessToken',
            json=token_data,
            timeout=10
        )
        token_result = token_resp.json()
        
        if not token_resp.ok or 'accessToken' not in token_result:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f"获取钉钉 AccessToken 失败: {token_result.get('message', '未知错误')}"
            )
        
        access_token = token_result.get('accessToken')
        
        # 检查 accessToken
        if not access_token:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f"钉钉返回数据不完整: {token_result}"
            )
        
        # 2. 根据 accessToken 获取用户信息
        headers = {"x-acs-dingtalk-access-token": access_token}
        user_resp = requests.get(
            'https://api.dingtalk.com/v1.0/contact/users/me',
            headers=headers,
            timeout=10
        )
        user_result = user_resp.json()
        
        if not user_resp.ok:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f"获取钉钉用户信息失败: {user_result.get('message', '未知错误')}"
            )
        
        # 从用户信息中获取 openId 或 unionId
        open_id = user_result.get('openId') or user_result.get('unionId')
        nick = user_result.get('nick')
        mobile = user_result.get('mobile')
        
        # 确保有唯一标识
        if not open_id:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f"无法获取用户唯一标识: {user_result}"
            )
        
        # 生成用户名（如果钉钉没有返回昵称或返回空字符串，则使用openId前8位）
        if not nick or nick.strip() == "":
            nick = f"dingtalk_{open_id[:8]}"
        else:
            nick = nick.strip()
        
        # 3. 使用抽象工厂模式创建或获取第三方登录用户
        # 实例化具体工厂
        factory = OAuthUserFactory()
        user, _, _ = await factory.get_or_create_oauth_user(
            provider="dingtalk",
            provider_user_id=open_id,
            username=nick,
            phone=mobile,
            extra_data={
                "nick": nick,
                "mobile": mobile,
                "openId": open_id
            }
        )
        
        # 4. 生成 JWT Token
        token_data = {"sub": str(user.id), "username": user.username}
        jwt_access_token = create_access_token(data=token_data)
        jwt_refresh_token = create_refresh_token(data=token_data)
        
        return LoginResponse(
            token=jwt_access_token,
            refresh_token=jwt_refresh_token,
            user=UserResponse.model_validate(user)
        )
        
    except requests.RequestException as e:
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail=f"钉钉服务请求失败: {str(e)}"
        )
    except Exception as e:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"登录处理失败: {str(e)}"
        )